/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package poker.neuralnetwork;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * Output perceptron is linear, does not use a squashing or discrete function.
 *
 * Hidden layer(s) uses sigmoid function.
 *
 * @author Benjamin L. Brodie <blbrodie@gmail.com>
 */
public class RegressionNN implements NeuralNetwork, Iterable<List<Edge>> {
    private List<LinearPerceptron> outputs;
    private List<Input> inputs;



    /**
     * Builds a regression type network with the specified layers, including
     * input layer and output layer.
     *
     *
     *
     * @param inputNum number of input nodes
     * @param outputNum number of linear output nodes
     * @param layers the number of hidden layers in the form:
     *        "5,2,3", where each value successive value represents the incoming,
     *        i.e., closer to input, number of hidden nodes in the layer, e.g.,
     *        the previously mentioned code would be a network where the first
     *        hidden layer has five nodes, second hidden layer has 2, and third
     *        and final layer, next to the output layer, has 3 nodes.
     */
    public RegressionNN(int inputNum, int outputNum, Integer... layers){
        if (inputNum  < 1){
            throw new IllegalArgumentException("must have inputs");
        }

        if (outputNum < 1){
            throw new IllegalArgumentException("must have outputs");
        }

        
        this.outputs = new ArrayList<LinearPerceptron>();


        for (int i = 0; i < outputNum; i++){
            outputs.add(new LinearPerceptron(connections(Arrays.asList(layers), inputNum)));
        }

        //collect the inputs
        inputs = collectInputs(outputs.get(0));
    }
    
    /* A recursion method to create new connections.
     * NOTICE: This ZERO's out weights. They must be set or randomized after this
     * method.
     */
    private List<Edge> connections(List<Integer> layers, int inputs){
        List<Edge> edges = new ArrayList<Edge>();
        
        //base case
        if (layers.size() == 1){
            for (int i = 0; i < layers.get(0); i++){
                for (int j = 0; j < inputs; j++){
                    edges.add(new Edge(new Input())); //ZERO OUT WEIGHT
                }
            }
        }
        
        //method
        else{
           int layer = layers.get(layers.size() - 1); //get the top layer

           for (int i = 0; i < layer; i++){
               edges.add(new Edge (new SigmoidPerceptron(connections(layers.subList(0, layers.size() -1), inputs))));
               edges.add(new Edge (new Threshold())); //the threshold perceptron
           }


        }



        return edges;



        
        
        
    }

    /**
     * Enter a list of input values and get the output value(s) from the NN.
     * @param inputVals
     * @param the values to be input
     * @return the list of outputs of the network.
     */
    public List<Double> process(List<Double> inputVals) {
        List<Double> outputVals = new ArrayList<Double>();

        //throw exception if there are more or less input values than the NN
        //was set up for
        if (inputVals.size() != inputs.size()){
            throw new IllegalArgumentException("The inputs don't equal the neural network inputs!");
        }


        Iterator<Input> inputIter = inputs.listIterator();

        //plug the inputs into the input nodes
        for (Double inputVal: inputVals){
            inputIter.next().setInput(inputVal); //set the input value
        }

        //get the output values list
        for (Perceptron output : outputs){
            outputVals.add( output.output());
        }

        return outputVals;

    }
    
    /* 
     * 
     * get the input nodes
     */
    private List<Input> collectInputs(Perceptron outputNode){
        List<Edge> inputLayer = null;
        List<Input> inputNodes = new ArrayList<Input>();

        for(List<Edge> layer : this){
            inputLayer = layer;
        }

        
        //inputLayer is now at the input layer
        for (Edge edge : inputLayer){
            inputNodes.add((Input)edge.getIncoming());
        }

        return inputNodes;

    }

//
//       OLD WAY OF DOING IT
//
//
//
//        List<Input> inputNodes;
//
//        //base case
//        if (outputNode.getEdges().get(0).getIncoming().getEdges() == null){
//            //this is the case where we are at the input!
//            //so, collect them
//            inputNodes = new ArrayList<Input>();
//
//            //TODO: convert edge to generics??
//            for (Edge edge :  outputNode.getEdges()){
//                inputNodes.add((Input)edge.getIncoming()); //is this safe???? should I use generics? yes, probably
//            }
//
//
//        }
//
//        else{
//            inputNodes = traverse(outputNode.getEdges().get(0).getIncoming());
//        }
//
//        return inputNodes;
//    }
//
//    public List<? extends Perceptron> getOutputNodes(){
//        return outputs;
    
    
    
    
    /*
     * 
     * ITERATION
     * 
     * 
     */

    public Iterator<List<Edge>> iterator() {
        return new NeuralNetIterator();
    }

    private class NeuralNetIterator implements Iterator<List<Edge>>{
        List<Edge> currentEdges = new ArrayList<Edge>();

        public NeuralNetIterator(){
            for (Perceptron out : outputs){
                currentEdges.addAll(out.getEdges());
            }
        }

        public boolean hasNext() {
            return currentEdges.get(0) != null;
        }


        //no remove option
        public void remove() {
            throw new UnsupportedOperationException();
        }

        public List<Edge> next() {
            List<Edge> current = new ArrayList<Edge>(currentEdges);
            currentEdges.clear();

            for (Edge edge : currentEdges){
                currentEdges.addAll(edge.getIncoming().getEdges());
            }

            return current;

        }

    }

    public String toString(){
        String s = "";

        s += "Outputs: ";

        int i = 0;
        for (Perceptron out : outputs){
            s += 
            i++;
        }
    }





}
